בשל השכיחות הגוברת של המחלה הזו, אני חושב שיש צורך במדריך קטן לטיפול בה. ובכן, תרופה מס' 1 זה כמובן Google, אבל למי שהתעצל לחפש או לא רצה להתעמק בדיונים ארוכים - תזכורת קטנה בשבילכם.
Cannot send session cookie - headers already sent by
(output started at script1.php:1) in script2.php on line 2
(output started at script1.php:1) in script2.php on line 2
אני מצאתי מילון (מקווה שאתם לפחות למדתם אנגלית בבית ספר יותר טוב ממני) ותרגמתי את המשפט כך:
לא יכול לשלוח את הסשן קוקי - כותרים כבר נשלחו עלי ידי הפלט שהתחיל ב...
פה אנחנו צריכים להכיר קצת את מבנה התקשורת ברשת. כאשר דפדפן ושרת מדברים אחד עם השני, הם שולחים אחד לשני מכתבים. כמו כל מכתב, המכתב הזה מורכב מראש מכתב, עם כתובת השולח, כתובת המען, לוגו של החברה, תאריך ועוד. כמו כן יש גם את גוף המכתב, שמכיל את כל פלט ה-html עצמו שעל הדפדפן להציג.
כל cookie (גם סשן קוקי) נשלח בראש המכתב ולא בתוכן המכתב. כלומר, תחילה על PHP לייצר את ראש המכתב - headers, ולאחריו לייצר רק את כל ה-html שאנחנו צריכים. ברגע שאנחנו מייצרים את פלט ה-html הראשון שלנו, PHP אוטומטית חושבת שסיימנו לייצר את ראש המכתב, כותבת אותו על דף וממשיכה לכתיבת התוכן.
אין לנו אפשרות להוסיף עוד לראש המכתב אחרי שכבר התחלנו לכתוב את התוכן; לא השארנו מקום.
קודם headerים, אחרי זה תוכן
השגיאה שלנו אומרת שבשורה 2 של script2.php אנחנו מנסים לשלוח header כלשהו, אבל עוד בשורה 1 של script1.php התחיל הפלט, ו-PHP חתמה את חלקת ה-headers. מה שעלינו לעשות הוא להעביר את הפלט אחרי יצירת הקוקי.
פלט יכול להיות פקודת echo כלשהי, טקסט שמופיע בתחילת הקובץ מחוץ לתגי ה-PHP, סימן רווח לפני התג הראשון או byte order mark עליו נדבר עוד מעט. כל שעלינו לעשות הוא לנטרל את הפלט ולדאוג לheaderים להישלח לפני הפלט. בואו ננסה ביחד
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Cannot send session cookie - headers already sent</title>
</head>
<body>
<?php
session_start(); // Sending header
if( $_GET['password'] == '1234' )
{
echo 'Correct password';
setcookie("loggedin",'true'); // Sending header
}
else
{
echo 'Incorrect password';
setcookie("loggedin",'false'); // Sending header
}
?>
</body>
</html>
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Cannot send session cookie - headers already sent</title>
</head>
<body>
<?php
session_start(); // Sending header
if( $_GET['password'] == '1234' )
{
echo 'Correct password';
setcookie("loggedin",'true'); // Sending header
}
else
{
echo 'Incorrect password';
setcookie("loggedin",'false'); // Sending header
}
?>
</body>
</html>
בדוגמה הזו יש לנו שלוש מקומות שבהם נוצר header, אבל, לפני שהם בכלל נוצרים מופיע לנו הרבה פלט שמועבר ללקוח כפי שהוא, עוד לפני ש-PHP בכלל מגיעה לקוד. אנחנו צריכים להעביר את כל קוד ה-headerים לראש העמוד, אבל את הפלט נרצה להשאיר בתוך ה-body, באמצע העמוד. יש רעיונות?
<?php
session_start(); // sending header
if( $_GET['password'] == '1234' )
{
$output = 'Correct password';
setcookie("loggedin",'true'); // sending header
}
else
{
$output = 'Incorrect password';
setcookie("loggedin",'false'); // sending header
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Cannot send session cookie - headers already sent</title>
</head>
<body>
<?php echo $output; ?>
</body>
</html>
session_start(); // sending header
if( $_GET['password'] == '1234' )
{
$output = 'Correct password';
setcookie("loggedin",'true'); // sending header
}
else
{
$output = 'Incorrect password';
setcookie("loggedin",'false'); // sending header
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Cannot send session cookie - headers already sent</title>
</head>
<body>
<?php echo $output; ?>
</body>
</html>
כל ה-headerים ייווצרו לפני כל פלט שהוא וההודעה שלנו תופיעה בדיוק איפה שרצינו.
מה אם אין לי שום פלט לפני? Byte Order Mark
בעולם המחשבים של היום קיימים קידודים שונים. קידוד הוא האופן שבו תווים נשמרים במחשב. יש קידודים, שבהם כל תו תופס בדיוק byte אחד, דוגמת windows-1255. כמות התווים השונים שיש בקידוד הזה היא מאוד מוגבלת. מחרוזת בקידוד הזה יכולה להכיל רק תווים מקבוצת תווים מסוימת. במקרה הזה אלו הם אותיות השפה העברית, השפה האנגלית, מספרים ועוד מעט תווים אחרים.
ואם נרצה לכתוב טקסט בשפה אחרת, שהקידוד הזה לא מכיר דרך לשמור ולייצג? כאן אנחנו נתקלים במובלות של הקידוד הזה. במקרה הזה נעבור לשימוש בקידוד שמכיל תווים מהשפה שבה אנחנו משתמשים. אבל גם הקידוד ההוא יכלול רק את אותה שפה, אנגלית מספרים ותווים מיוחדים.
UTF-8 הוא קידוד שמכיל תווים של הרבה שפות
לעומת זאת, הקידוד הזה מורכב מזוג של שני בתים לייצוג של כל תו. לקידוד הזה יש אח יותר גדול בשם utf-16 (שלנו אין בו שימוש). גם הוא מורכב משני בתים לתו, אבל לעומת utf-8 אין לו אחידות בנוגע לסדר הבתים בתו. מעבדים מסוימיים שומרים את הבית השני לפני הבית הראשון, מה שיוצר בלבול כאשר אותו קובץ מגיע למחשב עם מעבד אחר. הפתרון שנמצא היה לכתוב בתחילת הקובץ טקסט מיוחד שיסמל את סדר הבתים. שמו הוא, כפי שניחשתם נכון, הוא byte order mark.
משום מה, הרבה עורכי טקסט, וביניהם Notepad, דוחפים את ה-byte order mark גם לקבצים ששמורים בקידוד utf-8, למרות שאין לו שום קשר לשם. "byte order mark" אמנם לא מוצג על ידי עורכי טקסט, אך מבחינת מפענח ה-PHP הוא קיים שם ומועבר כפלט לדפדפן.
יש לכבות את ה-byte order mark בעורך הטקסט שלכם על מנת לפטור את הבעיה.
ואני מקווה שבעיית ה-hedearים לא תחזור אליכם שוב. :)
תגובות לכתבה:
תודה רבה
כיצד מכבים את ה-byte order mark?
בסביבות פיתוח (ide) כמו netbeans, zend studio, aptana הוא כבר מכובה.
ב notepad++ באחד התפריטים, איפה שאתה בוחר את הקידוד - יש שם encode as UTF8 without BOM
תודה.
ובנוטפאד רגיל? :)
אי אפשר.
אז למה אני לא מקבל את הטקסט הזה בתור פלט בקוד שאני כותב בנוטפאד הרגיל? :תוהה:
בתור איזה פלט? BOM לא הופך את הסקריפט ל"לא סקריפט". BOM זה כמו תו $. אם אתה שם דולר לפני התג הפותח של PHP הוא לא יפסיק לך את הסקריפט, נכון ? הוא בסה"כ יישלח לדפדפן. אותו דבר קורה עם ה BOM. הוא בסה"כ נשלח לדפדפן. לבטל אותו בנוטפאד אי אפשר.
"אך מבחינת מפענח ה-PHP הוא קיים שם ומועבר כפלט לדפדפן."
חשבתי שהכוונה היא שהוא מציג אותו.